/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: BasisLibrary.java,v 1.6 2006/06/20 21:51:58 spericas Exp $
 */

package com.sun.org.apache.xalan.internal.xsltc.runtime;

import com.sun.org.apache.xalan.internal.utils.SecuritySupport;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.FieldPosition;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.xml.transform.dom.DOMSource;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.Translet;
import com.sun.org.apache.xalan.internal.xsltc.dom.AbsoluteIterator;
import com.sun.org.apache.xml.internal.dtm.Axis;
import com.sun.org.apache.xalan.internal.xsltc.dom.DOMAdapter;
import com.sun.org.apache.xalan.internal.xsltc.dom.MultiDOM;
import com.sun.org.apache.xalan.internal.xsltc.dom.SingletonIterator;
import com.sun.org.apache.xalan.internal.xsltc.dom.StepIterator;
import com.sun.org.apache.xalan.internal.xsltc.dom.ArrayNodeListIterator;
import com.sun.org.apache.xml.internal.dtm.DTM;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.dtm.DTMManager;
import com.sun.org.apache.xml.internal.dtm.ref.DTMDefaultBase;
import com.sun.org.apache.xml.internal.dtm.ref.DTMNodeProxy;

import org.w3c.dom.DOMException;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.sun.org.apache.xml.internal.serializer.NamespaceMappings;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.org.apache.xml.internal.utils.XML11Char;

/**
 * Standard XSLT functions. All standard functions expect the current node
 * and the DOM as their last two arguments.
 */
public final class BasisLibrary {

  private final static String EMPTYSTRING = "";

  /**
   * Re-use a single instance of StringBuffer (per thread) in the basis library.
   * StringBuilder is better, however, DecimalFormat only accept StringBuffer
   */
  private static final ThreadLocal<StringBuilder> threadLocalStringBuilder =
      new ThreadLocal<StringBuilder>() {
        @Override
        protected StringBuilder initialValue() {
          return new StringBuilder();
        }
      };

  /**
   * ThreadLocal for StringBuffer used
   */
  private static final ThreadLocal<StringBuffer> threadLocalStringBuffer =
      new ThreadLocal<StringBuffer>() {
        @Override
        protected StringBuffer initialValue() {
          return new StringBuffer();
        }
      };

  /**
   * Standard function count(node-set)
   */
  public static int countF(DTMAxisIterator iterator) {
    return (iterator.getLast());
  }

  /**
   * Standard function position()
   *
   * @deprecated This method exists only for backwards compatibility with old translets.  New code
   * should not reference it.
   */
  public static int positionF(DTMAxisIterator iterator) {
    return iterator.isReverse()
        ? iterator.getLast() - iterator.getPosition() + 1
        : iterator.getPosition();
  }

  /**
   * XSLT Standard function sum(node-set).
   * stringToDouble is inlined
   */
  public static double sumF(DTMAxisIterator iterator, DOM dom) {
    try {
      double result = 0.0;
      int node;
      while ((node = iterator.next()) != DTMAxisIterator.END) {
        result += Double.parseDouble(dom.getStringValueX(node));
      }
      return result;
    } catch (NumberFormatException e) {
      return Double.NaN;
    }
  }

  /**
   * XSLT Standard function string()
   */
  public static String stringF(int node, DOM dom) {
    return dom.getStringValueX(node);
  }

  /**
   * XSLT Standard function string(value)
   */
  public static String stringF(Object obj, DOM dom) {
    if (obj instanceof DTMAxisIterator) {
      return dom.getStringValueX(((DTMAxisIterator) obj).reset().next());
    } else if (obj instanceof Node) {
      return dom.getStringValueX(((Node) obj).node);
    } else if (obj instanceof DOM) {
      return ((DOM) obj).getStringValue();
    } else {
      return obj.toString();
    }
  }

  /**
   * XSLT Standard function string(value)
   */
  public static String stringF(Object obj, int node, DOM dom) {
    if (obj instanceof DTMAxisIterator) {
      return dom.getStringValueX(((DTMAxisIterator) obj).reset().next());
    } else if (obj instanceof Node) {
      return dom.getStringValueX(((Node) obj).node);
    } else if (obj instanceof DOM) {
      // When the first argument is a DOM we want the whole
      // DOM and not just a single node - that would not make sense.
      //return ((DOM)obj).getStringValueX(node);
      return ((DOM) obj).getStringValue();
    } else if (obj instanceof Double) {
      Double d = (Double) obj;
      final String result = d.toString();
      final int length = result.length();
      if ((result.charAt(length - 2) == '.') &&
          (result.charAt(length - 1) == '0')) {
        return result.substring(0, length - 2);
      } else {
        return result;
      }
    } else {
      return obj != null ? obj.toString() : "";
    }
  }

  /**
   * XSLT Standard function number()
   */
  public static double numberF(int node, DOM dom) {
    return stringToReal(dom.getStringValueX(node));
  }

  /**
   * XSLT Standard function number(value)
   */
  public static double numberF(Object obj, DOM dom) {
    if (obj instanceof Double) {
      return ((Double) obj).doubleValue();
    } else if (obj instanceof Integer) {
      return ((Integer) obj).doubleValue();
    } else if (obj instanceof Boolean) {
      return ((Boolean) obj).booleanValue() ? 1.0 : 0.0;
    } else if (obj instanceof String) {
      return stringToReal((String) obj);
    } else if (obj instanceof DTMAxisIterator) {
      DTMAxisIterator iter = (DTMAxisIterator) obj;
      return stringToReal(dom.getStringValueX(iter.reset().next()));
    } else if (obj instanceof Node) {
      return stringToReal(dom.getStringValueX(((Node) obj).node));
    } else if (obj instanceof DOM) {
      return stringToReal(((DOM) obj).getStringValue());
    } else {
      final String className = obj.getClass().getName();
      runTimeError(INVALID_ARGUMENT_ERR, className, "number()");
      return 0.0;
    }
  }

  /**
   * XSLT Standard function round()
   */
  public static double roundF(double d) {
    return (d < -0.5 || d > 0.0) ? Math.floor(d + 0.5) : ((d == 0.0) ?
        d : (Double.isNaN(d) ? Double.NaN : -0.0));
  }

  /**
   * XSLT Standard function boolean()
   */
  public static boolean booleanF(Object obj) {
    if (obj instanceof Double) {
      final double temp = ((Double) obj).doubleValue();
      return temp != 0.0 && !Double.isNaN(temp);
    } else if (obj instanceof Integer) {
      return ((Integer) obj).doubleValue() != 0;
    } else if (obj instanceof Boolean) {
      return ((Boolean) obj).booleanValue();
    } else if (obj instanceof String) {
      return !((String) obj).equals(EMPTYSTRING);
    } else if (obj instanceof DTMAxisIterator) {
      DTMAxisIterator iter = (DTMAxisIterator) obj;
      return iter.reset().next() != DTMAxisIterator.END;
    } else if (obj instanceof Node) {
      return true;
    } else if (obj instanceof DOM) {
      String temp = ((DOM) obj).getStringValue();
      return !temp.equals(EMPTYSTRING);
    } else {
      final String className = obj.getClass().getName();
      runTimeError(INVALID_ARGUMENT_ERR, className, "boolean()");
    }
    return false;
  }

  /**
   * XSLT Standard function substring(). Must take a double because of
   * conversions resulting into NaNs and rounding.
   */
  public static String substringF(String value, double start) {
    if (Double.isNaN(start)) {
      return (EMPTYSTRING);
    }

    final int strlen = getStringLength(value);
    int istart = (int) Math.round(start) - 1;

    if (istart > strlen) {
      return (EMPTYSTRING);
    }
    if (istart < 1) {
      istart = 0;
    }
    try {
      istart = value.offsetByCodePoints(0, istart);
      return value.substring(istart);
    } catch (IndexOutOfBoundsException e) {
      runTimeError(RUN_TIME_INTERNAL_ERR, "substring()");
      return null;
    }
  }

  /**
   * XSLT Standard function substring(). Must take a double because of
   * conversions resulting into NaNs and rounding.
   */
  public static String substringF(String value, double start, double length) {
    if (Double.isInfinite(start) ||
        Double.isNaN(start) ||
        Double.isNaN(length) ||
        length < 0) {
      return (EMPTYSTRING);
    }

    int istart = (int) Math.round(start) - 1;
    int ilength = (int) Math.round(length);
    final int isum;
    if (Double.isInfinite(length)) {
      isum = Integer.MAX_VALUE;
    } else {
      isum = istart + ilength;
    }

    final int strlen = getStringLength(value);
    if (isum < 0 || istart > strlen) {
      return (EMPTYSTRING);
    }

    if (istart < 0) {
      ilength += istart;
      istart = 0;
    }

    try {
      istart = value.offsetByCodePoints(0, istart);
      if (isum > strlen) {
        return value.substring(istart);
      } else {
        int offset = value.offsetByCodePoints(istart, ilength);
        return value.substring(istart, offset);
      }
    } catch (IndexOutOfBoundsException e) {
      runTimeError(RUN_TIME_INTERNAL_ERR, "substring()");
      return null;
    }
  }

  /**
   * XSLT Standard function substring-after().
   */
  public static String substring_afterF(String value, String substring) {
    final int index = value.indexOf(substring);
    if (index >= 0) {
      return value.substring(index + substring.length());
    } else {
      return EMPTYSTRING;
    }
  }

  /**
   * XSLT Standard function substring-before().
   */
  public static String substring_beforeF(String value, String substring) {
    final int index = value.indexOf(substring);
    if (index >= 0) {
      return value.substring(0, index);
    } else {
      return EMPTYSTRING;
    }
  }

  /**
   * XSLT Standard function translate().
   */
  public static String translateF(String value, String from, String to) {
    final int tol = to.length();
    final int froml = from.length();
    final int valuel = value.length();

    final StringBuilder result = threadLocalStringBuilder.get();
    result.setLength(0);
    for (int j, i = 0; i < valuel; i++) {
      final char ch = value.charAt(i);
      for (j = 0; j < froml; j++) {
        if (ch == from.charAt(j)) {
          if (j < tol) {
            result.append(to.charAt(j));
          }
          break;
        }
      }
      if (j == froml) {
        result.append(ch);
      }
    }
    return result.toString();
  }

  /**
   * XSLT Standard function normalize-space().
   */
  public static String normalize_spaceF(int node, DOM dom) {
    return normalize_spaceF(dom.getStringValueX(node));
  }

  /**
   * XSLT Standard function normalize-space(string).
   */
  public static String normalize_spaceF(String value) {
    int i = 0, n = value.length();
    StringBuilder result = threadLocalStringBuilder.get();
    result.setLength(0);

    while (i < n && isWhiteSpace(value.charAt(i))) {
      i++;
    }

    while (true) {
      while (i < n && !isWhiteSpace(value.charAt(i))) {
        result.append(value.charAt(i++));
      }
      if (i == n) {
        break;
      }
      while (i < n && isWhiteSpace(value.charAt(i))) {
        i++;
      }
      if (i < n) {
        result.append(' ');
      }
    }
    return result.toString();
  }

  /**
   * XSLT Standard function generate-id().
   */
  public static String generate_idF(int node) {
    if (node > 0)
    // Only generate ID if node exists
    {
      return "N" + node;
    } else
    // Otherwise return an empty string
    {
      return EMPTYSTRING;
    }
  }

  /**
   * utility function for calls to local-name().
   */
  public static String getLocalName(String value) {
    int idx = value.lastIndexOf(':');
    if (idx >= 0) {
      value = value.substring(idx + 1);
    }
    idx = value.lastIndexOf('@');
    if (idx >= 0) {
      value = value.substring(idx + 1);
    }
    return (value);
  }

  /**
   * External functions that cannot be resolved are replaced with a call
   * to this method. This method will generate a runtime errors. A good
   * stylesheet checks whether the function exists using conditional
   * constructs, and never really tries to call it if it doesn't exist.
   * But simple stylesheets may result in a call to this method.
   * The compiler should generate a warning if it encounters a call to
   * an unresolved external function.
   */
  public static void unresolved_externalF(String name) {
    runTimeError(EXTERNAL_FUNC_ERR, name);
  }

  /**
   * Utility function to throw a runtime error on the use of an extension
   * function when the secure processing feature is set to true.
   */
  public static void unallowed_extension_functionF(String name) {
    runTimeError(UNALLOWED_EXTENSION_FUNCTION_ERR, name);
  }

  /**
   * Utility function to throw a runtime error on the use of an extension
   * element when the secure processing feature is set to true.
   */
  public static void unallowed_extension_elementF(String name) {
    runTimeError(UNALLOWED_EXTENSION_ELEMENT_ERR, name);
  }

  /**
   * Utility function to throw a runtime error for an unsupported element.
   *
   * This is only used in forward-compatibility mode, when the control flow
   * cannot be determined. In 1.0 mode, the error message is emitted at
   * compile time.
   */
  public static void unsupported_ElementF(String qname, boolean isExtension) {
    if (isExtension) {
      runTimeError(UNSUPPORTED_EXT_ERR, qname);
    } else {
      runTimeError(UNSUPPORTED_XSL_ERR, qname);
    }
  }

  /**
   * XSLT Standard function namespace-uri(node-set).
   */
  public static String namespace_uriF(DTMAxisIterator iter, DOM dom) {
    return namespace_uriF(iter.next(), dom);
  }

  /**
   * XSLT Standard function system-property(name)
   */
  public static String system_propertyF(String name) {
    if (name.equals("xsl:version")) {
      return ("1.0");
    }
    if (name.equals("xsl:vendor")) {
      return ("Apache Software Foundation (Xalan XSLTC)");
    }
    if (name.equals("xsl:vendor-url")) {
      return ("http://xml.apache.org/xalan-j");
    }

    runTimeError(INVALID_ARGUMENT_ERR, name, "system-property()");
    return (EMPTYSTRING);
  }

  /**
   * XSLT Standard function namespace-uri().
   */
  public static String namespace_uriF(int node, DOM dom) {
    final String value = dom.getNodeName(node);
    final int colon = value.lastIndexOf(':');
    if (colon >= 0) {
      return value.substring(0, colon);
    } else {
      return EMPTYSTRING;
    }
  }

  /**
   * Implements the object-type() extension function.
   *
   * @see <a href="http://www.exslt.org/">EXSLT</a>
   */
  public static String objectTypeF(Object obj) {
    if (obj instanceof String) {
      return "string";
    } else if (obj instanceof Boolean) {
      return "boolean";
    } else if (obj instanceof Number) {
      return "number";
    } else if (obj instanceof DOM) {
      return "RTF";
    } else if (obj instanceof DTMAxisIterator) {
      return "node-set";
    } else {
      return "unknown";
    }
  }

  /**
   * Implements the nodeset() extension function.
   */
  public static DTMAxisIterator nodesetF(Object obj) {
    if (obj instanceof DOM) {
      //final DOMAdapter adapter = (DOMAdapter) obj;
      final DOM dom = (DOM) obj;
      return new SingletonIterator(dom.getDocument(), true);
    } else if (obj instanceof DTMAxisIterator) {
      return (DTMAxisIterator) obj;
    } else {
      final String className = obj.getClass().getName();
      runTimeError(DATA_CONVERSION_ERR, "node-set", className);
      return null;
    }
  }

  //-- Begin utility functions

  private static boolean isWhiteSpace(char ch) {
    return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
  }

  private static boolean compareStrings(String lstring, String rstring,
      int op, DOM dom) {
    switch (op) {
      case Operators.EQ:
        return lstring.equals(rstring);

      case Operators.NE:
        return !lstring.equals(rstring);

      case Operators.GT:
        return numberF(lstring, dom) > numberF(rstring, dom);

      case Operators.LT:
        return numberF(lstring, dom) < numberF(rstring, dom);

      case Operators.GE:
        return numberF(lstring, dom) >= numberF(rstring, dom);

      case Operators.LE:
        return numberF(lstring, dom) <= numberF(rstring, dom);

      default:
        runTimeError(RUN_TIME_INTERNAL_ERR, "compare()");
        return false;
    }
  }

  /**
   * Utility function: node-set/node-set compare.
   */
  public static boolean compare(DTMAxisIterator left, DTMAxisIterator right,
      int op, DOM dom) {
    int lnode;
    left.reset();

    while ((lnode = left.next()) != DTMAxisIterator.END) {
      final String lvalue = dom.getStringValueX(lnode);

      int rnode;
      right.reset();
      while ((rnode = right.next()) != DTMAxisIterator.END) {
        // String value must be the same if both nodes are the same
        if (lnode == rnode) {
          if (op == Operators.EQ) {
            return true;
          } else if (op == Operators.NE) {
            continue;
          }
        }
        if (compareStrings(lvalue, dom.getStringValueX(rnode), op,
            dom)) {
          return true;
        }
      }
    }
    return false;
  }

  public static boolean compare(int node, DTMAxisIterator iterator,
      int op, DOM dom) {
    //iterator.reset();

    int rnode;
    String value;

    switch (op) {
      case Operators.EQ:
        rnode = iterator.next();
        if (rnode != DTMAxisIterator.END) {
          value = dom.getStringValueX(node);
          do {
            if (node == rnode
                || value.equals(dom.getStringValueX(rnode))) {
              return true;
            }
          } while ((rnode = iterator.next()) != DTMAxisIterator.END);
        }
        break;
      case Operators.NE:
        rnode = iterator.next();
        if (rnode != DTMAxisIterator.END) {
          value = dom.getStringValueX(node);
          do {
            if (node != rnode
                && !value.equals(dom.getStringValueX(rnode))) {
              return true;
            }
          } while ((rnode = iterator.next()) != DTMAxisIterator.END);
        }
        break;
      case Operators.LT:
        // Assume we're comparing document order here
        while ((rnode = iterator.next()) != DTMAxisIterator.END) {
          if (rnode > node) {
            return true;
          }
        }
        break;
      case Operators.GT:
        // Assume we're comparing document order here
        while ((rnode = iterator.next()) != DTMAxisIterator.END) {
          if (rnode < node) {
            return true;
          }
        }
        break;
    }
    return (false);
  }

  /**
   * Utility function: node-set/number compare.
   */
  public static boolean compare(DTMAxisIterator left, final double rnumber,
      final int op, DOM dom) {
    int node;
    //left.reset();

    switch (op) {
      case Operators.EQ:
        while ((node = left.next()) != DTMAxisIterator.END) {
          if (numberF(dom.getStringValueX(node), dom) == rnumber) {
            return true;
          }
        }
        break;

      case Operators.NE:
        while ((node = left.next()) != DTMAxisIterator.END) {
          if (numberF(dom.getStringValueX(node), dom) != rnumber) {
            return true;
          }
        }
        break;

      case Operators.GT:
        while ((node = left.next()) != DTMAxisIterator.END) {
          if (numberF(dom.getStringValueX(node), dom) > rnumber) {
            return true;
          }
        }
        break;

      case Operators.LT:
        while ((node = left.next()) != DTMAxisIterator.END) {
          if (numberF(dom.getStringValueX(node), dom) < rnumber) {
            return true;
          }
        }
        break;

      case Operators.GE:
        while ((node = left.next()) != DTMAxisIterator.END) {
          if (numberF(dom.getStringValueX(node), dom) >= rnumber) {
            return true;
          }
        }
        break;

      case Operators.LE:
        while ((node = left.next()) != DTMAxisIterator.END) {
          if (numberF(dom.getStringValueX(node), dom) <= rnumber) {
            return true;
          }
        }
        break;

      default:
        runTimeError(RUN_TIME_INTERNAL_ERR, "compare()");
    }

    return false;
  }

  /**
   * Utility function: node-set/string comparison.
   */
  public static boolean compare(DTMAxisIterator left, final String rstring,
      int op, DOM dom) {
    int node;
    //left.reset();
    while ((node = left.next()) != DTMAxisIterator.END) {
      if (compareStrings(dom.getStringValueX(node), rstring, op, dom)) {
        return true;
      }
    }
    return false;
  }


  public static boolean compare(Object left, Object right,
      int op, DOM dom) {
    boolean result = false;
    boolean hasSimpleArgs = hasSimpleType(left) && hasSimpleType(right);

    if (op != Operators.EQ && op != Operators.NE) {
      // If node-boolean comparison -> convert node to boolean
      if (left instanceof Node || right instanceof Node) {
        if (left instanceof Boolean) {
          right = new Boolean(booleanF(right));
          hasSimpleArgs = true;
        }
        if (right instanceof Boolean) {
          left = new Boolean(booleanF(left));
          hasSimpleArgs = true;
        }
      }

      if (hasSimpleArgs) {
        switch (op) {
          case Operators.GT:
            return numberF(left, dom) > numberF(right, dom);

          case Operators.LT:
            return numberF(left, dom) < numberF(right, dom);

          case Operators.GE:
            return numberF(left, dom) >= numberF(right, dom);

          case Operators.LE:
            return numberF(left, dom) <= numberF(right, dom);

          default:
            runTimeError(RUN_TIME_INTERNAL_ERR, "compare()");
        }
      }
      // falls through
    }

    if (hasSimpleArgs) {
      if (left instanceof Boolean || right instanceof Boolean) {
        result = booleanF(left) == booleanF(right);
      } else if (left instanceof Double || right instanceof Double ||
          left instanceof Integer || right instanceof Integer) {
        result = numberF(left, dom) == numberF(right, dom);
      } else { // compare them as strings
        result = stringF(left, dom).equals(stringF(right, dom));
      }

      if (op == Operators.NE) {
        result = !result;
      }
    } else {
      if (left instanceof Node) {
        left = new SingletonIterator(((Node) left).node);
      }
      if (right instanceof Node) {
        right = new SingletonIterator(((Node) right).node);
      }

      if (hasSimpleType(left) ||
          left instanceof DOM && right instanceof DTMAxisIterator) {
        // swap operands and operator
        final Object temp = right;
        right = left;
        left = temp;
        op = Operators.swapOp(op);
      }

      if (left instanceof DOM) {
        if (right instanceof Boolean) {
          result = ((Boolean) right).booleanValue();
          return result == (op == Operators.EQ);
        }

        final String sleft = ((DOM) left).getStringValue();

        if (right instanceof Number) {
          result = ((Number) right).doubleValue() ==
              stringToReal(sleft);
        } else if (right instanceof String) {
          result = sleft.equals((String) right);
        } else if (right instanceof DOM) {
          result = sleft.equals(((DOM) right).getStringValue());
        }

        if (op == Operators.NE) {
          result = !result;
        }
        return result;
      }

      // Next, node-set/t for t in {real, string, node-set, result-tree}

      DTMAxisIterator iter = ((DTMAxisIterator) left).reset();

      if (right instanceof DTMAxisIterator) {
        result = compare(iter, (DTMAxisIterator) right, op, dom);
      } else if (right instanceof String) {
        result = compare(iter, (String) right, op, dom);
      } else if (right instanceof Number) {
        final double temp = ((Number) right).doubleValue();
        result = compare(iter, temp, op, dom);
      } else if (right instanceof Boolean) {
        boolean temp = ((Boolean) right).booleanValue();
        result = (iter.reset().next() != DTMAxisIterator.END) == temp;
      } else if (right instanceof DOM) {
        result = compare(iter, ((DOM) right).getStringValue(),
            op, dom);
      } else if (right == null) {
        return (false);
      } else {
        final String className = right.getClass().getName();
        runTimeError(INVALID_ARGUMENT_ERR, className, "compare()");
      }
    }
    return result;
  }

  /**
   * Utility function: used to test context node's language
   */
  public static boolean testLanguage(String testLang, DOM dom, int node) {
    // language for context node (if any)
    String nodeLang = dom.getLanguage(node);
    if (nodeLang == null) {
      return (false);
    } else {
      nodeLang = nodeLang.toLowerCase();
    }

    // compare context node's language agains test language
    testLang = testLang.toLowerCase();
    if (testLang.length() == 2) {
      return (nodeLang.startsWith(testLang));
    } else {
      return (nodeLang.equals(testLang));
    }
  }

  private static boolean hasSimpleType(Object obj) {
    return obj instanceof Boolean || obj instanceof Double ||
        obj instanceof Integer || obj instanceof String ||
        obj instanceof Node || obj instanceof DOM;
  }

  /**
   * Utility function: used in StringType to convert a string to a real.
   */
  public static double stringToReal(String s) {
    try {
      return Double.valueOf(s).doubleValue();
    } catch (NumberFormatException e) {
      return Double.NaN;
    }
  }

  /**
   * Utility function: used in StringType to convert a string to an int.
   */
  public static int stringToInt(String s) {
    try {
      return Integer.parseInt(s);
    } catch (NumberFormatException e) {
      return (-1); // ???
    }
  }

  private static final int DOUBLE_FRACTION_DIGITS = 340;
  private static final double lowerBounds = 0.001;
  private static final double upperBounds = 10000000;
  private static DecimalFormat defaultFormatter, xpathFormatter;
  private static String defaultPattern = "";

  static {
    NumberFormat f = NumberFormat.getInstance(Locale.getDefault());
    defaultFormatter = (f instanceof DecimalFormat) ?
        (DecimalFormat) f : new DecimalFormat();
    // Set max fraction digits so that truncation does not occur. Setting
    // the max to Integer.MAX_VALUE may cause problems with some JDK's.
    defaultFormatter.setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
    defaultFormatter.setMinimumFractionDigits(0);
    defaultFormatter.setMinimumIntegerDigits(1);
    defaultFormatter.setGroupingUsed(false);

    // This formatter is used to convert numbers according to the XPath
    // 1.0 syntax which ignores locales (http://www.w3.org/TR/xpath#NT-Number)
    xpathFormatter = new DecimalFormat("",
        new DecimalFormatSymbols(Locale.US));
    xpathFormatter.setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
    xpathFormatter.setMinimumFractionDigits(0);
    xpathFormatter.setMinimumIntegerDigits(1);
    xpathFormatter.setGroupingUsed(false);
  }

  /**
   * Utility function: used in RealType to convert a real to a string.
   * Removes the decimal if null. Uses a specialized formatter object
   * for very large and very small numbers that ignores locales, thus
   * using always using "." as a decimal separator.
   */
  public static String realToString(double d) {
    final double m = Math.abs(d);
    if ((m >= lowerBounds) && (m < upperBounds)) {
      final String result = Double.toString(d);
      final int length = result.length();
      // Remove leading zeros.
      if ((result.charAt(length - 2) == '.') &&
          (result.charAt(length - 1) == '0')) {
        return result.substring(0, length - 2);
      } else {
        return result;
      }
    } else {
      if (Double.isNaN(d) || Double.isInfinite(d)) {
        return (Double.toString(d));
      }

      //Convert -0.0 to +0.0 other values remains the same
      d = d + 0.0;

      // Use the XPath formatter to ignore locales
      StringBuffer result = threadLocalStringBuffer.get();
      result.setLength(0);
      xpathFormatter.format(d, result, _fieldPosition);
      return result.toString();
    }
  }

  /**
   * Utility function: used in RealType to convert a real to an integer
   */
  public static int realToInt(double d) {
    return (int) d;
  }

  /**
   * Utility function: used to format/adjust  a double to a string. The
   * DecimalFormat object comes from the 'formatSymbols' hashtable in
   * AbstractTranslet.
   */
  private static FieldPosition _fieldPosition = new FieldPosition(0);

  public static String formatNumber(double number, String pattern,
      DecimalFormat formatter) {
    // bugzilla fix 12813
    if (formatter == null) {
      formatter = defaultFormatter;
    }
    try {
      StringBuffer result = threadLocalStringBuffer.get();
      result.setLength(0);
      if (pattern != defaultPattern) {
        formatter.applyLocalizedPattern(pattern);
      }
      formatter.format(number, result, _fieldPosition);
      return result.toString();
    } catch (IllegalArgumentException e) {
      runTimeError(FORMAT_NUMBER_ERR, Double.toString(number), pattern);
      return (EMPTYSTRING);
    }
  }

  /**
   * Utility function: used to convert references to node-sets. If the
   * obj is an instanceof Node then create a singleton iterator.
   */
  public static DTMAxisIterator referenceToNodeSet(Object obj) {
    // Convert var/param -> node
    if (obj instanceof Node) {
      return (new SingletonIterator(((Node) obj).node));
    }
    // Convert var/param -> node-set
    else if (obj instanceof DTMAxisIterator) {
      return (((DTMAxisIterator) obj).cloneIterator().reset());
    } else {
      final String className = obj.getClass().getName();
      runTimeError(DATA_CONVERSION_ERR, className, "node-set");
      return null;
    }
  }

  /**
   * Utility function: used to convert reference to org.w3c.dom.NodeList.
   */
  public static NodeList referenceToNodeList(Object obj, DOM dom) {
    if (obj instanceof Node || obj instanceof DTMAxisIterator) {
      DTMAxisIterator iter = referenceToNodeSet(obj);
      return dom.makeNodeList(iter);
    } else if (obj instanceof DOM) {
      dom = (DOM) obj;
      return dom.makeNodeList(DTMDefaultBase.ROOTNODE);
    } else {
      final String className = obj.getClass().getName();
      runTimeError(DATA_CONVERSION_ERR, className,
          "org.w3c.dom.NodeList");
      return null;
    }
  }

  /**
   * Utility function: used to convert reference to org.w3c.dom.Node.
   */
  public static org.w3c.dom.Node referenceToNode(Object obj, DOM dom) {
    if (obj instanceof Node || obj instanceof DTMAxisIterator) {
      DTMAxisIterator iter = referenceToNodeSet(obj);
      return dom.makeNode(iter);
    } else if (obj instanceof DOM) {
      dom = (DOM) obj;
      DTMAxisIterator iter = dom.getChildren(DTMDefaultBase.ROOTNODE);
      return dom.makeNode(iter);
    } else {
      final String className = obj.getClass().getName();
      runTimeError(DATA_CONVERSION_ERR, className, "org.w3c.dom.Node");
      return null;
    }
  }

  /**
   * Utility function: used to convert reference to long.
   */
  public static long referenceToLong(Object obj) {
    if (obj instanceof Number) {
      return ((Number) obj).longValue();    // handles Integer and Double
    } else {
      final String className = obj.getClass().getName();
      runTimeError(DATA_CONVERSION_ERR, className, Long.TYPE);
      return 0;
    }
  }

  /**
   * Utility function: used to convert reference to double.
   */
  public static double referenceToDouble(Object obj) {
    if (obj instanceof Number) {
      return ((Number) obj).doubleValue();   // handles Integer and Double
    } else {
      final String className = obj.getClass().getName();
      runTimeError(DATA_CONVERSION_ERR, className, Double.TYPE);
      return 0;
    }
  }

  /**
   * Utility function: used to convert reference to boolean.
   */
  public static boolean referenceToBoolean(Object obj) {
    if (obj instanceof Boolean) {
      return ((Boolean) obj).booleanValue();
    } else {
      final String className = obj.getClass().getName();
      runTimeError(DATA_CONVERSION_ERR, className, Boolean.TYPE);
      return false;
    }
  }

  /**
   * Utility function: used to convert reference to String.
   */
  public static String referenceToString(Object obj, DOM dom) {
    if (obj instanceof String) {
      return (String) obj;
    } else if (obj instanceof DTMAxisIterator) {
      return dom.getStringValueX(((DTMAxisIterator) obj).reset().next());
    } else if (obj instanceof Node) {
      return dom.getStringValueX(((Node) obj).node);
    } else if (obj instanceof DOM) {
      return ((DOM) obj).getStringValue();
    } else {
      final String className = obj.getClass().getName();
      runTimeError(DATA_CONVERSION_ERR, className, String.class);
      return null;
    }
  }

  /**
   * Utility function used to convert a w3c Node into an internal DOM iterator.
   */
  public static DTMAxisIterator node2Iterator(org.w3c.dom.Node node,
      Translet translet, DOM dom) {
    final org.w3c.dom.Node inNode = node;
    // Create a dummy NodeList which only contains the given node to make
    // use of the nodeList2Iterator() interface.
    org.w3c.dom.NodeList nodelist = new org.w3c.dom.NodeList() {
      public int getLength() {
        return 1;
      }

      public org.w3c.dom.Node item(int index) {
        if (index == 0) {
          return inNode;
        } else {
          return null;
        }
      }
    };

    return nodeList2Iterator(nodelist, translet, dom);
  }

  /**
   * In a perfect world, this would be the implementation for
   * nodeList2Iterator. In reality, though, this causes a
   * ClassCastException in getDTMHandleFromNode because SAXImpl is
   * not an instance of DOM2DTM. So we use the more lengthy
   * implementation below until this issue has been addressed.
   *
   * @see org.apache.xml.dtm.ref.DTMManagerDefault#getDTMHandleFromNode
   */
  private static DTMAxisIterator nodeList2IteratorUsingHandleFromNode(
      org.w3c.dom.NodeList nodeList,
      Translet translet, DOM dom) {
    final int n = nodeList.getLength();
    final int[] dtmHandles = new int[n];
    DTMManager dtmManager = null;
    if (dom instanceof MultiDOM) {
      dtmManager = ((MultiDOM) dom).getDTMManager();
    }
    for (int i = 0; i < n; ++i) {
      org.w3c.dom.Node node = nodeList.item(i);
      int handle;
      if (dtmManager != null) {
        handle = dtmManager.getDTMHandleFromNode(node);
      } else if (node instanceof DTMNodeProxy
          && ((DTMNodeProxy) node).getDTM() == dom) {
        handle = ((DTMNodeProxy) node).getDTMNodeNumber();
      } else {
        runTimeError(RUN_TIME_INTERNAL_ERR, "need MultiDOM");
        return null;
      }
      dtmHandles[i] = handle;
      System.out.println("Node " + i + " has handle 0x" +
          Integer.toString(handle, 16));
    }
    return new ArrayNodeListIterator(dtmHandles);
  }

  /**
   * Utility function used to convert a w3c NodeList into a internal
   * DOM iterator.
   */
  public static DTMAxisIterator nodeList2Iterator(
      org.w3c.dom.NodeList nodeList,
      Translet translet, DOM dom) {
    // First pass: build w3c DOM for all nodes not proxied from our DOM.
    //
    // Notice: this looses some (esp. parent) context for these nodes,
    // so some way to wrap the original nodes inside a DTMAxisIterator
    // might be preferable in the long run.
    int n = 0; // allow for change in list length, just in case.
    Document doc = null;
    DTMManager dtmManager = null;
    int[] proxyNodes = new int[nodeList.getLength()];
    if (dom instanceof MultiDOM) {
      dtmManager = ((MultiDOM) dom).getDTMManager();
    }
    for (int i = 0; i < nodeList.getLength(); ++i) {
      org.w3c.dom.Node node = nodeList.item(i);
      if (node instanceof DTMNodeProxy) {
        DTMNodeProxy proxy = (DTMNodeProxy) node;
        DTM nodeDTM = proxy.getDTM();
        int handle = proxy.getDTMNodeNumber();
        boolean isOurDOM = (nodeDTM == dom);
        if (!isOurDOM && dtmManager != null) {
          try {
            isOurDOM = (nodeDTM == dtmManager.getDTM(handle));
          } catch (ArrayIndexOutOfBoundsException e) {
            // invalid node handle, so definitely not our doc
          }
        }
        if (isOurDOM) {
          proxyNodes[i] = handle;
          ++n;
          continue;
        }
      }
      proxyNodes[i] = DTM.NULL;
      int nodeType = node.getNodeType();
      if (doc == null) {
        if (dom instanceof MultiDOM == false) {
          runTimeError(RUN_TIME_INTERNAL_ERR, "need MultiDOM");
          return null;
        }
        try {
          AbstractTranslet at = (AbstractTranslet) translet;
          doc = at.newDocument("", "__top__");
        } catch (javax.xml.parsers.ParserConfigurationException e) {
          runTimeError(RUN_TIME_INTERNAL_ERR, e.getMessage());
          return null;
        }
      }
      // Use one dummy element as container for each node of the
      // list. That way, it is easier to detect resp. avoid
      // funny things which change the number of nodes,
      // e.g. auto-concatenation of text nodes.
      Element mid;
      switch (nodeType) {
        case org.w3c.dom.Node.ELEMENT_NODE:
        case org.w3c.dom.Node.TEXT_NODE:
        case org.w3c.dom.Node.CDATA_SECTION_NODE:
        case org.w3c.dom.Node.COMMENT_NODE:
        case org.w3c.dom.Node.ENTITY_REFERENCE_NODE:
        case org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE:
          mid = doc.createElementNS(null, "__dummy__");
          mid.appendChild(doc.importNode(node, true));
          doc.getDocumentElement().appendChild(mid);
          ++n;
          break;
        case org.w3c.dom.Node.ATTRIBUTE_NODE:
          // The mid element also serves as a container for
          // attributes, avoiding problems with conflicting
          // attributes or node order.
          mid = doc.createElementNS(null, "__dummy__");
          mid.setAttributeNodeNS((Attr) doc.importNode(node, true));
          doc.getDocumentElement().appendChild(mid);
          ++n;
          break;
        default:
          // Better play it safe for all types we aren't sure we know
          // how to deal with.
          runTimeError(RUN_TIME_INTERNAL_ERR,
              "Don't know how to convert node type "
                  + nodeType);
      }
    }

    // w3cDOM -> DTM -> DOMImpl
    DTMAxisIterator iter = null, childIter = null, attrIter = null;
    if (doc != null) {
      final MultiDOM multiDOM = (MultiDOM) dom;
      DOM idom = (DOM) dtmManager.getDTM(new DOMSource(doc), false,
          null, true, false);
      // Create DOMAdapter and register with MultiDOM
      DOMAdapter domAdapter = new DOMAdapter(idom,
          translet.getNamesArray(),
          translet.getUrisArray(),
          translet.getTypesArray(),
          translet.getNamespaceArray());
      multiDOM.addDOMAdapter(domAdapter);

      DTMAxisIterator iter1 = idom.getAxisIterator(Axis.CHILD);
      DTMAxisIterator iter2 = idom.getAxisIterator(Axis.CHILD);
      iter = new AbsoluteIterator(
          new StepIterator(iter1, iter2));

      iter.setStartNode(DTMDefaultBase.ROOTNODE);

      childIter = idom.getAxisIterator(Axis.CHILD);
      attrIter = idom.getAxisIterator(Axis.ATTRIBUTE);
    }

    // Second pass: find DTM handles for every node in the list.
    int[] dtmHandles = new int[n];
    n = 0;
    for (int i = 0; i < nodeList.getLength(); ++i) {
      if (proxyNodes[i] != DTM.NULL) {
        dtmHandles[n++] = proxyNodes[i];
        continue;
      }
      org.w3c.dom.Node node = nodeList.item(i);
      DTMAxisIterator iter3 = null;
      int nodeType = node.getNodeType();
      switch (nodeType) {
        case org.w3c.dom.Node.ELEMENT_NODE:
        case org.w3c.dom.Node.TEXT_NODE:
        case org.w3c.dom.Node.CDATA_SECTION_NODE:
        case org.w3c.dom.Node.COMMENT_NODE:
        case org.w3c.dom.Node.ENTITY_REFERENCE_NODE:
        case org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE:
          iter3 = childIter;
          break;
        case org.w3c.dom.Node.ATTRIBUTE_NODE:
          iter3 = attrIter;
          break;
        default:
          // Should not happen, as first run should have got all these
          throw new InternalRuntimeError("Mismatched cases");
      }
      if (iter3 != null) {
        iter3.setStartNode(iter.next());
        dtmHandles[n] = iter3.next();
        // For now, play it self and perform extra checks:
        if (dtmHandles[n] == DTMAxisIterator.END) {
          throw new InternalRuntimeError("Expected element missing at " + i);
        }
        if (iter3.next() != DTMAxisIterator.END) {
          throw new InternalRuntimeError("Too many elements at " + i);
        }
        ++n;
      }
    }
    if (n != dtmHandles.length) {
      throw new InternalRuntimeError("Nodes lost in second pass");
    }

    return new ArrayNodeListIterator(dtmHandles);
  }

  /**
   * Utility function used to convert references to DOMs.
   */
  public static DOM referenceToResultTree(Object obj) {
    try {
      return ((DOM) obj);
    } catch (IllegalArgumentException e) {
      final String className = obj.getClass().getName();
      runTimeError(DATA_CONVERSION_ERR, "reference", className);
      return null;
    }
  }

  /**
   * Utility function: used with nth position filters to convert a sequence
   * of nodes to just one single node (the one at position n).
   */
  public static DTMAxisIterator getSingleNode(DTMAxisIterator iterator) {
    int node = iterator.next();
    return (new SingletonIterator(node));
  }

  /**
   * Utility function: used in xsl:copy.
   */
  private static char[] _characterArray = new char[32];

  public static void copy(Object obj,
      SerializationHandler handler,
      int node,
      DOM dom) {
    try {
      if (obj instanceof DTMAxisIterator) {
        DTMAxisIterator iter = (DTMAxisIterator) obj;
        dom.copy(iter.reset(), handler);
      } else if (obj instanceof Node) {
        dom.copy(((Node) obj).node, handler);
      } else if (obj instanceof DOM) {
        //((DOM)obj).copy(((com.sun.org.apache.xml.internal.dtm.ref.DTMDefaultBase)((DOMAdapter)obj).getDOMImpl()).getDocument(), handler);
        DOM newDom = (DOM) obj;
        newDom.copy(newDom.getDocument(), handler);
      } else {
        String string = obj.toString();         // or call stringF()
        final int length = string.length();
        if (length > _characterArray.length) {
          _characterArray = new char[length];
        }
        string.getChars(0, length, _characterArray, 0);
        handler.characters(_characterArray, 0, length);
      }
    } catch (SAXException e) {
      runTimeError(RUN_TIME_COPY_ERR);
    }
  }

  /**
   * Utility function to check if xsl:attribute has a valid qname
   * This method should only be invoked if the name attribute is an AVT
   */
  public static void checkAttribQName(String name) {
    final int firstOccur = name.indexOf(":");
    final int lastOccur = name.lastIndexOf(":");
    final String localName = name.substring(lastOccur + 1);

    if (firstOccur > 0) {
      final String newPrefix = name.substring(0, firstOccur);

      if (firstOccur != lastOccur) {
        final String oriPrefix = name.substring(firstOccur + 1, lastOccur);
        if (!XML11Char.isXML11ValidNCName(oriPrefix)) {
          // even though the orignal prefix is ignored, it should still get checked for valid NCName
          runTimeError(INVALID_QNAME_ERR, oriPrefix + ":" + localName);
        }
      }

      // prefix must be a valid NCName
      if (!XML11Char.isXML11ValidNCName(newPrefix)) {
        runTimeError(INVALID_QNAME_ERR, newPrefix + ":" + localName);
      }
    }

    // local name must be a valid NCName and must not be XMLNS
    if ((!XML11Char.isXML11ValidNCName(localName)) || (localName.equals(Constants.XMLNS_PREFIX))) {
      runTimeError(INVALID_QNAME_ERR, localName);
    }
  }

  /**
   * Utility function to check if a name is a valid ncname
   * This method should only be invoked if the attribute value is an AVT
   */
  public static void checkNCName(String name) {
    if (!XML11Char.isXML11ValidNCName(name)) {
      runTimeError(INVALID_NCNAME_ERR, name);
    }
  }

  /**
   * Utility function to check if a name is a valid qname
   * This method should only be invoked if the attribute value is an AVT
   */
  public static void checkQName(String name) {
    if (!XML11Char.isXML11ValidQName(name)) {
      runTimeError(INVALID_QNAME_ERR, name);
    }
  }

  /**
   * Utility function for the implementation of xsl:element.
   */
  public static String startXslElement(String qname, String namespace,
      SerializationHandler handler, DOM dom, int node) {
    try {
      // Get prefix from qname
      String prefix;
      final int index = qname.indexOf(':');

      if (index > 0) {
        prefix = qname.substring(0, index);

        // Handle case when prefix is not known at compile time
        if (namespace == null || namespace.length() == 0) {
          try {
            // not sure if this line of code ever works
            namespace = dom.lookupNamespace(node, prefix);
          } catch (RuntimeException e) {
            handler.flushPending();  // need to flush or else can't get namespacemappings
            NamespaceMappings nm = handler.getNamespaceMappings();
            namespace = nm.lookupNamespace(prefix);
            if (namespace == null) {
              runTimeError(NAMESPACE_PREFIX_ERR, prefix);
            }
          }
        }

        handler.startElement(namespace, qname.substring(index + 1),
            qname);
        handler.namespaceAfterStartElement(prefix, namespace);
      } else {
        // Need to generate a prefix?
        if (namespace != null && namespace.length() > 0) {
          prefix = generatePrefix();
          qname = prefix + ':' + qname;
          handler.startElement(namespace, qname, qname);
          handler.namespaceAfterStartElement(prefix, namespace);
        } else {
          handler.startElement(null, null, qname);
        }
      }
    } catch (SAXException e) {
      throw new RuntimeException(e.getMessage());
    }

    return qname;
  }

  /**
   * This function is used in the execution of xsl:element
   */
  public static String getPrefix(String qname) {
    final int index = qname.indexOf(':');
    return (index > 0) ? qname.substring(0, index) : null;
  }

  /**
   * This function is used in the execution of xsl:element
   */
  private static int prefixIndex = 0;

  public static String generatePrefix() {
    synchronized (BasisLibrary.class) {
      return ("ns" + prefixIndex++);
    }
  }

  public static final String RUN_TIME_INTERNAL_ERR =
      "RUN_TIME_INTERNAL_ERR";
  public static final String RUN_TIME_COPY_ERR =
      "RUN_TIME_COPY_ERR";
  public static final String DATA_CONVERSION_ERR =
      "DATA_CONVERSION_ERR";
  public static final String EXTERNAL_FUNC_ERR =
      "EXTERNAL_FUNC_ERR";
  public static final String EQUALITY_EXPR_ERR =
      "EQUALITY_EXPR_ERR";
  public static final String INVALID_ARGUMENT_ERR =
      "INVALID_ARGUMENT_ERR";
  public static final String FORMAT_NUMBER_ERR =
      "FORMAT_NUMBER_ERR";
  public static final String ITERATOR_CLONE_ERR =
      "ITERATOR_CLONE_ERR";
  public static final String AXIS_SUPPORT_ERR =
      "AXIS_SUPPORT_ERR";
  public static final String TYPED_AXIS_SUPPORT_ERR =
      "TYPED_AXIS_SUPPORT_ERR";
  public static final String STRAY_ATTRIBUTE_ERR =
      "STRAY_ATTRIBUTE_ERR";
  public static final String STRAY_NAMESPACE_ERR =
      "STRAY_NAMESPACE_ERR";
  public static final String NAMESPACE_PREFIX_ERR =
      "NAMESPACE_PREFIX_ERR";
  public static final String DOM_ADAPTER_INIT_ERR =
      "DOM_ADAPTER_INIT_ERR";
  public static final String PARSER_DTD_SUPPORT_ERR =
      "PARSER_DTD_SUPPORT_ERR";
  public static final String NAMESPACES_SUPPORT_ERR =
      "NAMESPACES_SUPPORT_ERR";
  public static final String CANT_RESOLVE_RELATIVE_URI_ERR =
      "CANT_RESOLVE_RELATIVE_URI_ERR";
  public static final String UNSUPPORTED_XSL_ERR =
      "UNSUPPORTED_XSL_ERR";
  public static final String UNSUPPORTED_EXT_ERR =
      "UNSUPPORTED_EXT_ERR";
  public static final String UNKNOWN_TRANSLET_VERSION_ERR =
      "UNKNOWN_TRANSLET_VERSION_ERR";
  public static final String INVALID_QNAME_ERR = "INVALID_QNAME_ERR";
  public static final String INVALID_NCNAME_ERR = "INVALID_NCNAME_ERR";
  public static final String UNALLOWED_EXTENSION_FUNCTION_ERR = "UNALLOWED_EXTENSION_FUNCTION_ERR";
  public static final String UNALLOWED_EXTENSION_ELEMENT_ERR = "UNALLOWED_EXTENSION_ELEMENT_ERR";

  // All error messages are localized and are stored in resource bundles.
  private static ResourceBundle m_bundle;

  public final static String ERROR_MESSAGES_KEY = "error-messages";

  static {
    String resource = "com.sun.org.apache.xalan.internal.xsltc.runtime.ErrorMessages";
    m_bundle = SecuritySupport.getResourceBundle(resource);
  }

  /**
   * Print a run-time error message.
   */
  public static void runTimeError(String code) {
    throw new RuntimeException(m_bundle.getString(code));
  }

  public static void runTimeError(String code, Object[] args) {
    final String message = MessageFormat.format(m_bundle.getString(code),
        args);
    throw new RuntimeException(message);
  }

  public static void runTimeError(String code, Object arg0) {
    runTimeError(code, new Object[]{arg0});
  }

  public static void runTimeError(String code, Object arg0, Object arg1) {
    runTimeError(code, new Object[]{arg0, arg1});
  }

  public static void consoleOutput(String msg) {
    System.out.println(msg);
  }

  /**
   * Replace a certain character in a string with a new substring.
   */
  public static String replace(String base, char ch, String str) {
    return (base.indexOf(ch) < 0) ? base :
        replace(base, String.valueOf(ch), new String[]{str});
  }

  public static String replace(String base, String delim, String[] str) {
    final int len = base.length();
    final StringBuilder result = threadLocalStringBuilder.get();
    result.setLength(0);

    for (int i = 0; i < len; i++) {
      final char ch = base.charAt(i);
      final int k = delim.indexOf(ch);

      if (k >= 0) {
        result.append(str[k]);
      } else {
        result.append(ch);
      }
    }
    return result.toString();
  }


  /**
   * Utility method to allow setting parameters of the form
   * {namespaceuri}localName
   * which get mapped to an instance variable in the class
   * Hence  a parameter of the form "{http://foo.bar}xyz"
   * will be replaced with the corresponding values
   * by the BasisLibrary's utility method mapQNametoJavaName
   * and thus get mapped to legal java variable names
   */
  public static String mapQNameToJavaName(String base) {
    return replace(base, ".-:/{}?#%*",
        new String[]{"$dot$", "$dash$", "$colon$", "$slash$",
            "", "$colon$", "$ques$", "$hash$", "$per$",
            "$aster$"});

  }

  /**
   * Utility method to calculate string-length as a number of code points,
   * to avoid possible errors with string that contains
   * complementary characters
   */
  public static int getStringLength(String str) {
    return str.codePointCount(0, str.length());
  }

  //-- End utility functions
}
